package io.le.starter.lock.locker;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;

/**
 * createTime：2024-12-03 14:35
 * description：
 */
public class RedisLockSimple extends AbstractLock {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    //增加锁的默认过期时间(30000ms)
    public final long defaultExpireTime = 30000;
    //锁的唯一uuid
    public final String uuid;

    private Long expireTime;

    public RedisLockSimple(RedisTemplate<String, Object> redisTemplate, String lockName, String uuid) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuid = uuid;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long durationTime, TimeUnit unit) throws InterruptedException {
        return this.tryLockTime(durationTime, defaultExpireTime, unit);
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public void lock() {

        lockExpireTime(defaultExpireTime, TimeUnit.MILLISECONDS);
    }

    public void lockExpireTime(Long time, TimeUnit timeUnit) {
        //不建议使用
        this.expireTime = time;
        while (true) {
            //使用lua脚本加锁
            //String luaScript = "if (redis.call('exists', KEYS[1]) == 0) then redis.call('set', KEYS[1], ARGV[1]) redis.call('pexpire', KEYS[1], ARGV[2])  return  1  else  return  0 end";

            //可重入
            String luaScript = "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', KEYS[1], ARGV[1], 1) redis.call('pexpire', KEYS[1], ARGV[2]) return 1 end if (redis.call('hexists', KEYS[1], ARGV[1]) == 1) then redis.call('hincrby', KEYS[1], ARGV[1], 1) redis.call('pexpire', KEYS[1], ARGV[2]) return 1 else return 0 end";
            DefaultRedisScript<Long> longDefaultRedisScript = new DefaultRedisScript<>(luaScript, Long.class);
            Long result = redisTemplate.execute(longDefaultRedisScript, Collections.singletonList(this.lockName), uuid, time.toString());

            //使用redis setIfAbsent加锁
            //Boolean result = redisTemplate.opsForValue().setIfAbsent(lockName, uuid, time, timeUnit);
            if (Objects.nonNull(result) && result.equals(1L)) {
                //lua脚本实现锁自动续期，业务逻辑没有执行完毕，但是锁却是过期释放掉了，就需要重新开启一个线程来进行锁的续期
                new Thread(() -> {
                    while (true) {
                        String expireScript = "if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then return 0 else redis.call('pexpire', KEYS[1], ARGV[2]) return 1 end";
                        DefaultRedisScript<Long> expireScriptRedis = new DefaultRedisScript<>(expireScript, Long.class);
                        Long expireResult = redisTemplate.execute(expireScriptRedis, Collections.singletonList(this.lockName), uuid, time.toString());
                        if (Objects.isNull(expireResult) || expireResult.equals(0L)) {
                            break;
                        }
                        try {
                            Thread.sleep(time / 2);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }).start();
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void unlock() {
        //使用lua脚本解锁
        //String luaScript = "if (redis.call('exists', KEYS[1]) == 0) then return 0 end if (redis.call('get', KEYS[1])) == ARGV[1] then redis.call('del', KEYS[1]) return 1 else return 0 end";

        //可重入
        String luaScript = "if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then return 0 end local lockCount = redis.call('hincrby', KEYS[1], ARGV[1], -1) if (lockCount > 0) then redis.call('pexpire', KEYS[1], ARGV[2]) return 1 else redis.call('del', KEYS[1]) return 1 end";
        DefaultRedisScript<Long> longDefaultRedisScript = new DefaultRedisScript<>(luaScript, Long.class);
        redisTemplate.execute(longDefaultRedisScript, Collections.singletonList(lockName), uuid, expireTime);
        //当前持有锁的线程是否是本线程，是的话才删除
//        String result = String.valueOf(redisTemplate.opsForValue().get(lockName));
//        if (!uuid.equalsIgnoreCase(result)) {
//            return;
//        }
//        redisTemplate.opsForValue().getOperations().delete(lockName);
    }


    public boolean tryLockTime(long durationTime, long expireTime, TimeUnit timeUnit) {
        long startTime = System.nanoTime();
        long currentTime = System.nanoTime();
        boolean lockResult = false;
        while (currentTime - startTime <= durationTime) {
            //获取锁
            boolean result = this.tryLockDetail(expireTime, timeUnit);
            if (result) {
                lockResult = true;
                break;
            }
            currentTime = System.nanoTime();
        }

        return lockResult;

    }


    public boolean tryLockDetail(Long lockExpireTime, TimeUnit timeUnit) {
        //不建议使用
        this.expireTime = lockExpireTime;

        //使用lua脚本加锁
        //String luaScript = "if (redis.call('exists', KEYS[1]) == 0) then redis.call('set', KEYS[1], ARGV[1]) redis.call('pexpire', KEYS[1], ARGV[2])  return  1  else  return  0 end";

        //可重入
        String luaScript = "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', KEYS[1], ARGV[1], 1) redis.call('pexpire', KEYS[1], ARGV[2]) return 1 end if (redis.call('hexists', KEYS[1], ARGV[1]) == 1) then redis.call('hincrby', KEYS[1], ARGV[1], 1) redis.call('pexpire', KEYS[1], ARGV[2]) return 1 else return 0 end";
        DefaultRedisScript<Long> longDefaultRedisScript = new DefaultRedisScript<>(luaScript, Long.class);
        Long result = redisTemplate.execute(longDefaultRedisScript, Collections.singletonList(this.lockName), uuid, lockExpireTime.toString());

        //使用redis setIfAbsent加锁
        //Boolean result = redisTemplate.opsForValue().setIfAbsent(lockName, uuid, time, timeUnit);
        if (Objects.nonNull(result) && result.equals(1L)) {
            //lua脚本实现锁自动续期，业务逻辑没有执行完毕，但是锁却是过期释放掉了，就需要重新开启一个线程来进行锁的续期
            new Thread(() -> {
                while (true) {
                    String expireScript = "if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then return 0 else redis.call('pexpire', KEYS[1], ARGV[2]) return 1 end";
                    DefaultRedisScript<Long> expireScriptRedis = new DefaultRedisScript<>(expireScript, Long.class);
                    Long expireResult = redisTemplate.execute(expireScriptRedis, Collections.singletonList(this.lockName), uuid, lockExpireTime.toString());
                    if (Objects.isNull(expireResult) || expireResult.equals(0L)) {
                        break;
                    }
                    try {
                        Thread.sleep(lockExpireTime / 2);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }).start();
            return true;
        }
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }
}
